如何安全优雅的对存量数据迁移Data Migration
数据库重构后的存量数据迁移(Data Migration)是软件工程中风险较高但又必须面对的环节。你提出**“通过 CSV 文件整理需要处理的数据”**,这是一个非常好的思路!CSV 作为中间态,既方便人工审查(Review),又可以作为数据快照(Snapshot)进行版本控制。
要做到**“安全且优雅”**,核心原则是:解耦、幂等、可回滚、可测试。
以下是一套基于 CSV 的优雅数据迁移方案,分为 4 个阶段:
阶段一:架构设计(ETL 思想)
不要试图写一个直接从旧表 INSERT INTO 新表的超级 SQL,这极易出错且难以调试。建议采用经典的 ETL(提取-转换-加载) 模式:
- E (Extract) 提取:将旧表数据原封不动导出为
old_data.csv。 - T (Transform) 转换:写一个 Python 脚本(推荐使用
pandas),读取old_data.csv,在内存中进行数据清洗、拆分、字段映射,生成符合新表结构的new_table_A.csv,new_table_B.csv。 - L (Load) 加载:写一个专门的导入脚本,将生成好的新 CSV 安全地写入新数据库。
阶段二:数据转换(Transform)—— 优雅的核心
在这个阶段,你完全脱离了数据库,只对 CSV 文件进行操作。
优势:
- 安全:怎么折腾都不会弄坏数据库。
- 可追溯:你可以把生成的
new_table_A.csv发给业务方确认,或者自己用 Excel 打开检查外键映射对不对。
示例代码思路 (Python + Pandas):
import pandas as pd
import uuid
# 1. 读取旧数据
df_old = pd.read_csv('old_users.csv')
# 2. 数据清洗与拆分(假设旧的 user 表被拆分成了 users 和 employees 两张表)
df_users = pd.DataFrame()
df_users['id'] = df_old['id']
df_users['username'] = df_old['login_name']
df_users['password'] = df_old['pwd']
df_employees = pd.DataFrame()
df_employees['no'] = df_old['id'].apply(lambda x: f"EMP{x:04d}") # 生成新工号
df_employees['user_id'] = df_old['id']
df_employees['name'] = df_old['real_name']
df_employees['department_id'] = df_old['dept_id'].fillna(1) # 处理空值
# 3. 导出为新的 CSV(准备导入)
df_users.to_csv('import_users.csv', index=False)
df_employees.to_csv('import_employees.csv', index=False)
阶段三:数据加载(Load)—— 安全的保障
将清洗好的 CSV 导入新表时,必须保证脚本的幂等性(Idempotence)和事务性。
1. 保证入库顺序(拓扑排序)
新表之间肯定有外键依赖(比如先有 users 才能有 employees)。必须严格按照依赖顺序读取 CSV 并入库。
2. 幂等性设计(Idempotency)
这是最重要的一点! 迁移脚本可能会因为网络波动或脏数据在中途崩溃。你的脚本必须支持**“重复运行无数次,结果都一样”**。
- 方案 A:基于主键的
UPSERT(推荐) 如果数据已存在则更新(或跳过),不存在则插入。在 SQLAlchemy 中可以使用ON CONFLICT DO NOTHING(PostgreSQL) 或INSERT IGNORE(MySQL)。 - 方案 B:先查后插 插入前先查一下 ID 是否存在。